package com.smsc.headend.task.engine.executor.impl.metercontrol;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.smsc.headend.common.enums.errorcode.TaskExecuteErrorCode;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.dmo.ComConfigDMO;
import com.smsc.headend.module.asset.dmo.MeterConfigDMO;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.common.entity.DeviceOperation;
import com.smsc.headend.module.data.dmo.DeviceSAPandKeys;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.data.enums.DictionaryType;
import com.smsc.headend.module.data.enums.OperationGroup;
import com.smsc.headend.module.data.enums.OperationName;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.report.entity.LogConnectDisConnectHis;
import com.smsc.headend.module.report.entity.LogModeSwitchHisSub;
import com.smsc.headend.module.report.entity.LogScheduleTaskDevice;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.dto.AtomicTaskState;
import com.smsc.headend.module.task.dto.Task;
import com.smsc.headend.module.task.enums.SubTaskStatus;
import com.smsc.headend.module.task.enums.TaskType;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.task.engine.exception.UDISTaskExecutorException;
import com.smsc.headend.task.engine.executor.TaskExecutor;
import com.smsc.headend.task.engine.facade.DataItemFacade;
import com.smsc.headend.task.engine.handler.impl.metercontrol.RemoteActionHandler;
import com.smsc.headend.task.engine.mapper.LogConnectDisConnectHisMapper;
import com.smsc.headend.task.engine.service.ConnectorService;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import com.smsc.headend.task.engine.service.task.LogScheduleTaskDeviceService;
import com.smsc.headend.task.engine.service.task.TaskPacketService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;

/**
 * @author wxh
 * @date 2020/11/19 20:18
 */
@Slf4j
public class RemoteControlActionExecutor implements TaskExecutor {
    @Autowired
    TaskPacketService packetService;
    @Autowired
    ConnectorService connectorService;
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    FeignAssetManagementService assetManagementRemoteService;
    @Autowired
    DataItemFacade dataItemFacade;
    @Autowired
    TaskDAO taskDAO;
    @Autowired
    AssetService assetService;
    @Autowired
    LogConnectDisConnectHisMapper logConnectDisConnectHisMapper;
    @Autowired
    LogScheduleTaskDeviceService logScheduleTaskDeviceService;

    @Override
    public AtomicTaskState execute(Task task, AtomicTask atomicTask) throws UDISTaskExecutorException {
        DeviceOperation dictionary = assetService.getDictionary(atomicTask.getOperationId(), DictionaryType.Operation.getType());
        Meter meter = getMeter(task, assetService);
        if (task.getMeterId() != null && meter == null) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.METER_NOT_EXISTS.toString());
        }
        ComEntity terminal = assetService.getComById(task.getComId());
        ComConfigDMO comConfigDMO = assetService.getComConfigById(task.getComId());
        String interfaceType = comConfigDMO.getInterfaceType();
        Boolean isShortName = comConfigDMO.getIsShortName();
        MeterConfigDMO meterConfigDMO = null;
        if (task.getMeterId() != null) {
            meterConfigDMO = assetService.getMeterConfigById(task.getMeterId());
            interfaceType = meterConfigDMO.getInterfaceType();
            isShortName = meterConfigDMO.getIsShortName();
        }
        List<DataItemDTO> dataItemDTOList = dataItemFacade.getDataItemsByOperation(meter, terminal, dictionary, getParameterMap(task, atomicTask), comConfigDMO.getModelId());
        if (CollectionUtils.isEmpty(dataItemDTOList)) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.DATAITEM_NOT_EXISTS.toString());
        }
        DeviceSAPandKeys deviceKeys = meter != null ? assetManagementRemoteService.getMeterCommunicationKey(meter.getMeterId(), dataItemDTOList.get(0).getRoleId()) :
                assetManagementRemoteService.getTerminalCommunicationKey(terminal.getComId(), dataItemDTOList.get(0).getRoleId());
        byte[] packets = packetService.disconnectControlAction(dataItemDTOList.get(0), interfaceType, isShortName, deviceKeys);
        if (packets == null) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.PACKETS_IS_NULL.toString());
        }
        log.info(ByteUtils.byteToHexString(packets));
        connectorService.sendPacket(task, atomicTask, dataItemDTOList.get(0).getRoleId(), task.getIsODR(), packets);

        Class handlerClass = getHandlerClass(atomicTask);
        return createAtomicState(handlerClass);
    }

    @Override
    public Boolean handleFailure(Task task, AtomicTask atomicTask, String message) {
        String key = "message";
        if (atomicTask.getGroupId() == OperationGroup.ConnectArmed.getId().intValue()
                || atomicTask.getGroupId() == OperationGroup.Disconnect.getId().intValue()
                || atomicTask.getGroupId() == OperationGroup.CheckCdc.getId().intValue()) {
            if (task.getTaskType().equals(TaskType.SwitchMeterPayment.toString())) {
                Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
                Long taskId = MapUtil.getLong(parameterMap, "taskId");
                assetManagementRemoteService.updateStepStatus(taskId, task.getMeterId(), atomicTask.getGroupId() == OperationGroup.ConnectArmed.getId().intValue() ? LogModeSwitchHisSub.Step.Connect : LogModeSwitchHisSub.Step.Disconnect, SubTaskStatus.Failed.getCode(), null, SystemClock.now() / 1000, "execute " + OperationName.forId(atomicTask.getOperationId()).getName() + " fail");
            } else {
                Map<String, Object> m = JSONUtil.toBean(task.getParamJson(), Map.class);
                key = m.get("seq") != null ? Convert.toStr(m.get("seq")) : key;
                String stepOperationName = OperationName.forId(atomicTask.getOperationId()).getName();
                taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), key, String.format("step %s failed, [%s]", stepOperationName, message), 3600l, false);
                updateLog(task.getMessageId(), task.getMeterId(), LogConnectDisConnectHis.Result.failed.getCode(), message);
            }

        }
        if (TaskType.MeterPlanCdc.toString().equalsIgnoreCase(task.getTaskType())) {
            logScheduleTaskDeviceService.sendMessage(task, LogScheduleTaskDevice.StatusEnum.Failed.getCode(), LogScheduleTaskDevice.StatusDescEnum.DecodeDataFailed.getCode());
        }
        return true;
    }

    private Class getHandlerClass(AtomicTask atomicTask) throws UDISTaskExecutorException {
        Class cla = null;
        switch (OperationGroup.forId(atomicTask.getGroupId().intValue())) {
            case Connect:
            case ConnectArmed:
            case Disconnect:
                cla = RemoteActionHandler.class;
                break;
            default:
                throw new UDISTaskExecutorException(TaskExecuteErrorCode.UNEXPECTED_VALUE.toString() + atomicTask.getGroupId().intValue());
        }
        return cla;
    }

    public void updateLog(String messageId, Long meterId, Integer result, String reason) {
        List<LogConnectDisConnectHis> logConnectDisConnectHisList = logConnectDisConnectHisMapper.selectList(Wrappers.<LogConnectDisConnectHis>lambdaQuery()
                .eq(LogConnectDisConnectHis::getMessageId, messageId).eq(LogConnectDisConnectHis::getDeviceId, meterId).orderByDesc(LogConnectDisConnectHis::getTvUpdate));
        if (null != logConnectDisConnectHisList && !logConnectDisConnectHisList.isEmpty()) {
            LogConnectDisConnectHis logConnectDisConnectHis = logConnectDisConnectHisList.get(0);
            logConnectDisConnectHis.setResult(result);
            logConnectDisConnectHis.setReason(reason);
            logConnectDisConnectHisMapper.updateById(logConnectDisConnectHis);
        }
    }

}
